package org.bjf.aop;

import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.bjf.exception.ServiceException;
import org.bjf.modules.core.web.core.ThreadContext;
import org.bjf.utils.IpUtil;
import org.slf4j.MDC;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

/**
 * 日志拦截器
 *
 * @author bjf
 */
@Slf4j
@Component
@Order(Ordered.LOWEST_PRECEDENCE)
public class LogInterceptor implements HandlerInterceptor {

    private static final String TRACE_ID = "traceId";
    private static final ThreadLocal<Long> TIME_LOCAL = new ThreadLocal<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // sleuth生成的traceId
        String traceId = request.getHeader(TRACE_ID);
        if (StringUtils.isNotBlank(traceId)) {
            MDC.put(TRACE_ID, traceId);
        }
        String requestBody = getRequestBody(request);
        // 打印请求参数
        log.info("start uri:{},params:{},contentType:{},ip:{},user-agent:{}",
                request.getRequestURI(),
                requestBody,
                request.getContentType(),
                IpUtil.getIp(request), request.getHeader("User-Agent"));
        request = new HttpServletRequestWrapper(request) {
            @Override
            public ServletInputStream getInputStream() throws IOException {
                ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestBody.getBytes(StandardCharsets.UTF_8));
                return new ByteArrayServletInputStream(byteArrayInputStream);
            }
        };
        TIME_LOCAL.set(System.currentTimeMillis());
        return Boolean.TRUE;
    }

    private String getRequestBody(HttpServletRequest request) {
        String contentType = request.getContentType();
        if (MediaType.APPLICATION_JSON_VALUE.equals(contentType) || MediaType.APPLICATION_XML.equals(contentType)) {
            try {
                String body = IOUtils.toString(request.getInputStream(),StandardCharsets.UTF_8);
                return body;
            } catch (IOException e) {
                throw new ServiceException(e.getMessage(), e);
            }
        } else {
            return JSON.toJSONString(request.getParameterMap());
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        // 打印响应时间
        Long start = TIME_LOCAL.get();
        long consumeTime = System.currentTimeMillis() - start;
        String uri = request.getRequestURI();
        log.info("end uri:{},response time:{}", uri, consumeTime);

        if (consumeTime > 1000) {
            log.warn("uri:[{}]响应时间过长:[{} millis]", uri, consumeTime);
        }

        try {
            TIME_LOCAL.remove();
            ThreadContext.clear();
        } catch (Exception ignore) {
        }

        MDC.clear();
    }

    private static class ByteArrayServletInputStream extends ServletInputStream {
        private ByteArrayInputStream byteArrayInputStream;

        public ByteArrayServletInputStream(ByteArrayInputStream byteArrayInputStream) {
            this.byteArrayInputStream = byteArrayInputStream;
        }

        @Override
        public boolean isFinished() {
            return false;
        }

        @Override
        public boolean isReady() {
            return false;
        }

        @Override
        public void setReadListener(ReadListener readListener) {

        }

        @Override
        public int read() throws IOException {
            return byteArrayInputStream.read();
        }
    }
}
